import { MessageData, MessageHeaders, MessageProperties, ProcessResult } from '@/types';
import { generateUUID } from '@/utils/uuid';

/**
 * Message class representing a message in the system
 */
export class Message {
  public readonly id: string;
  public readonly body: unknown;
  public readonly headers: MessageHeaders;
  public readonly properties: MessageProperties;
  private _timestamp: number;
  public retryCount: number;

  public get timestamp(): number {
    return this._timestamp;
  }

  protected setTimestamp(timestamp: number): void {
    this._timestamp = timestamp;
  }

  constructor(
    body: unknown,
    headers: MessageHeaders = {},
    properties: MessageProperties = {},
    id?: string
  ) {
    this.id = id || generateUUID();
    this.body = body;
    this.headers = { ...headers };
    this.properties = { ...properties };
    this._timestamp = Date.now();
    this.retryCount = 0;
  }

  /**
   * Add a header to the message
   */
  addHeader(key: string, value: string | number | boolean): Message {
    this.headers[key] = value;
    return this;
  }

  /**
   * Add multiple headers to the message
   */
  addHeaders(headers: MessageHeaders): Message {
    Object.assign(this.headers, headers);
    return this;
  }

  /**
   * Get a header value
   */
  getHeader(key: string): string | number | boolean | undefined {
    return this.headers[key];
  }

  /**
   * Add a property to the message
   */
  addProperty(key: string, value: unknown): Message {
    this.properties[key] = value;
    return this;
  }

  /**
   * Add multiple properties to the message
   */
  addProperties(properties: MessageProperties): Message {
    Object.assign(this.properties, properties);
    return this;
  }

  /**
   * Get a property value
   */
  getProperty(key: string): unknown {
    return this.properties[key];
  }

  /**
   * Increment retry count
   */
  incrementRetry(): Message {
    this.retryCount++;
    return this;
  }

  /**
   * Check if message has expired based on TTL
   */
  isExpired(ttlMs?: number): boolean {
    if (!ttlMs) return false;
    return Date.now() - this.timestamp > ttlMs;
  }

  /**
   * Get message age in milliseconds
   */
  getAge(): number {
    return Date.now() - this.timestamp;
  }

  /**
   * Convert message to plain object
   */
  toObject(): MessageData {
    return {
      id: this.id,
      body: this.body,
      headers: { ...this.headers },
      properties: { ...this.properties },
      timestamp: this.timestamp,
      retryCount: this.retryCount,
    };
  }

  /**
   * Create message from plain object
   */
  static fromObject(data: MessageData): Message {
    const message = new Message(data.body, data.headers, data.properties, data.id);
    message.retryCount = data.retryCount;
    // Note: timestamp is set in constructor, but we override it here
    message.setTimestamp(data.timestamp);
    return message;
  }

  /**
   * Clone the message
   */
  clone(): Message {
    const cloned = new Message(this.body, { ...this.headers }, { ...this.properties }, this.id);
    cloned.retryCount = this.retryCount;
    cloned.setTimestamp(this.timestamp);
    return cloned;
  }

  /**
   * Create a typed message
   */
  static typed<T>(
    body: T,
    headers?: MessageHeaders,
    properties?: MessageProperties
  ): TypedMessage<T> {
    return new TypedMessage(body, headers, properties);
  }
}

/**
 * Typed message class for type-safe message handling
 */
export class TypedMessage<T> extends Message {
  public readonly body: T;

  constructor(
    body: T,
    headers: MessageHeaders = {},
    properties: MessageProperties = {},
    id?: string
  ) {
    super(body, headers, properties, id);
    this.body = body;
  }

  /**
   * Clone the typed message
   */
  clone(): TypedMessage<T> {
    const cloned = new TypedMessage(
      this.body,
      { ...this.headers },
      { ...this.properties },
      this.id
    );
    cloned.retryCount = this.retryCount;
    cloned.setTimestamp(this.timestamp);
    return cloned;
  }
}

/**
 * ProcessResult class representing the result of message processing
 */
export class MessageProcessResult implements ProcessResult {
  public readonly success: boolean;
  public readonly message?: string;
  public readonly data?: unknown;
  public readonly shouldRetry: boolean;

  constructor(success: boolean, message?: string, data?: unknown, shouldRetry = false) {
    this.success = success;
    this.message = message || undefined;
    this.data = data;
    this.shouldRetry = shouldRetry;
  }

  /**
   * Create a successful result
   */
  static success(data?: unknown, message = 'Success'): MessageProcessResult {
    return new MessageProcessResult(true, message, data, false);
  }

  /**
   * Create a failure result
   */
  static failure(message = 'Failed', shouldRetry = false): MessageProcessResult {
    return new MessageProcessResult(false, message, undefined, shouldRetry);
  }

  /**
   * Create a retry result
   */
  static retry(message = 'Retry required'): MessageProcessResult {
    return new MessageProcessResult(false, message, undefined, true);
  }

  /**
   * Create result from error
   */
  static fromError(error: Error, shouldRetry = false): MessageProcessResult {
    return new MessageProcessResult(false, error.message, undefined, shouldRetry);
  }

  /**
   * Convert to plain object
   */
  toObject(): ProcessResult {
    return {
      success: this.success,
      message: this.message || undefined,
      data: this.data,
      shouldRetry: this.shouldRetry,
    };
  }
}

/**
 * Message builder for fluent message creation
 */
export class MessageBuilder {
  private body: unknown;
  private headers: MessageHeaders = {};
  private properties: MessageProperties = {};
  private id?: string;

  constructor(body: unknown) {
    this.body = body;
  }

  /**
   * Set message ID
   */
  withId(id: string): MessageBuilder {
    this.id = id;
    return this;
  }

  /**
   * Add header
   */
  withHeader(key: string, value: string | number | boolean): MessageBuilder {
    this.headers[key] = value;
    return this;
  }

  /**
   * Add multiple headers
   */
  withHeaders(headers: MessageHeaders): MessageBuilder {
    Object.assign(this.headers, headers);
    return this;
  }

  /**
   * Add property
   */
  withProperty(key: string, value: unknown): MessageBuilder {
    this.properties[key] = value;
    return this;
  }

  /**
   * Add multiple properties
   */
  withProperties(properties: MessageProperties): MessageBuilder {
    Object.assign(this.properties, properties);
    return this;
  }

  /**
   * Set priority
   */
  withPriority(priority: number): MessageBuilder {
    this.properties.priority = priority;
    return this;
  }

  /**
   * Set expiration
   */
  withExpiration(expiration: number): MessageBuilder {
    this.properties.expiration = expiration;
    return this;
  }

  /**
   * Set correlation ID
   */
  withCorrelationId(correlationId: string): MessageBuilder {
    this.properties.correlationId = correlationId;
    return this;
  }

  /**
   * Set reply-to queue
   */
  withReplyTo(replyTo: string): MessageBuilder {
    this.properties.replyTo = replyTo;
    return this;
  }

  /**
   * Build the message
   */
  build(): Message {
    return new Message(this.body, this.headers, this.properties, this.id);
  }

  /**
   * Build a typed message
   */
  buildTyped<T>(): TypedMessage<T> {
    return new TypedMessage(this.body as T, this.headers, this.properties, this.id);
  }
}

/**
 * Utility functions for message creation
 */
export const createMessage = (body: unknown): MessageBuilder => {
  return new MessageBuilder(body);
};

export const createTypedMessage = <T>(body: T): TypedMessage<T> => {
  return new TypedMessage(body);
};
